/*
	Copyright (c) 2011 Andrey O. Zbitnev (azbitnev@gmail.com)
	Licensed under the MIT License (LICENSE.txt).

	$Id: jquery.ajp.autocomplete.js 79 2012-03-14 12:55:47Z azbitnev@gmail.com $
*/

(function($){

	if (!$.ajp) $.ajp = { }
	$.ajp.autocomplete = { version: '0.14pa', forms: {}, last: false }

	$.fn.extend({

		ajp$autocomplete: function(options) {

			var defaults = {  

				deltaX: 0,
				deltaY: 0,
				paddingWidth: 0,

				caching: true,

				getUrl: function (needle) {
					return { url: './autocomplete/', params: { 'needle': needle } }
				},

				selectItem: function (ctx, i) { ctx.selected = i },

				parseResponse: function (ctx, resp) {

					ctx.remoteResponse = resp

					var variants = []
					for (var i = 0; i < resp.variants.length; i ++)
						variants.push(resp.variants[i])

					ctx.setVariants(variants)
					ctx.show()
				}
			}

			var options = $.extend(defaults, options);

			$('form').each(function (i, el) { if (!$(el).attr('id')) el.id = 'form-autogenerated-id-' + i })

			return this.each(function(i, e) {

				var api = {

					requestId: 0,
					list: null,
					target: null,
					variants: [],
					selected: -1,
					cache: {},
					skipOne: false,
			
					init: function (target) {

						this.target = target
						this.list = document.createElement('div')
						var objList = $(this.list)
						objList.css('display', 'none').addClass('ajp-autocomplete-list')
						document.body.appendChild(this.list)

						if ($.browser.msie)
							objList.addClass('ajp-autocomplete-list-msie')

						var ctx = this
						var form = $($(this.target).attr('form'))
						if (!$.ajp.autocomplete.forms[form.attr('id')]) {
							$.ajp.autocomplete.forms[form.attr('id')] = true
							form.submit(function (evt) {
								var lists = $('.ajp-autocomplete-list:visible')
								if (lists.length > 0) {
									if (!/explorer/i.test(navigator.appName))
										lists.css({ display: 'none' })
									return false
								}
								return true
							})
							form.find('button[type=submit], input[type=submit]').focus(function () {
								if ($('.ajp-autocomplete-list:visible').length > 0 && $.ajp.autocomplete.last) {
									$($.ajp.autocomplete.last.target).focus();
									$.ajp.autocomplete.last.acquire()
								}
							})
						}
					},

					cancelEvent: function (evt) {
						evt.cancelBubble = true
						if (evt.stopPropagation) {
							evt.stopPropagation()
							evt.preventDefault()
						}
					},

					acquire: function (target) {
						this.hide()
						this.setVariants([])
					},

					setVariants: function (variants, req) {
						if (req === undefined || req >= this.requestId) {
							this.variants = variants
							options.selectItem(this, -1)
						}
					},

					hide: function () {
						$('.ajp-autocomplete-list').css('display', 'none').html('<!-- -->')
					},

					show: function () {

						var html = '';
						var itemClass = ' autocomplete-item-odd';
						for (var i = 0; i < this.variants.length; i ++) {
							if (typeof this.variants[i] == 'string') {
								itemClass = (itemClass.length > 0 ? '' : ' autocomplete-item-odd');
								html += '<div class="autocomplete-item ' + itemClass + (i == this.selected ? ' autocomplete-item-selected' : '') + '">'
									+ this.variants[i]
									+ '<input type="hidden" value="' + i + '"/>'
									+ '</div>';
							}
						}

						var ctx = this;
						$(this.list).html(html).find('.autocomplete-item').mouseover(function (evt) {
							options.selectItem(ctx, $(evt.currentTarget).find('input[type=hidden]:eq(0)').val());
							ctx.show();
						})

						var l = $(this.list);
						var t = $(this.target);
						var pos = t.offset();
						var offsetY = options.deltaY + parseInt(t.css('border-top-width'))
							+ parseInt(t.css('border-bottom-width'))
							+ parseInt(l.css('border-top-width'))
						;
						var paddingWidth = options.paddingWidth //+ parseInt(t.css('border-left-width'))
							//+ parseInt(t.css('border-right-width'))
							- parseInt(l.css('border-left-width'))
							- parseInt(l.css('border-right-width'))
						;
						var display = (this.variants.length > 0 ? '' : 'none');
						l.css({
							left: '' + (pos.left + options.deltaX) + 'px',
							top: '' + (pos.top + t.height() + offsetY - parseInt(t.css('border-bottom-width'))) + 'px',
							//width: '' + (t.width() + paddingWidth) + 'px',
							width: '' + (t.outerWidth() + paddingWidth) + 'px',
							'display': display
						});
					},

					refresh: function () {

						var ctx = this
						var needle = this.target.value

						if (options.caching && this.cache[needle]) {
							var data = this.cache[needle]
							if (data) {
								options.parseResponse(ctx, data)
								return
							}
						}

						var where = options.getUrl;
						if (typeof where == 'function')
							where = where(needle);
						where.params.requestId = ++ this.requestId;
						$.getJSON(where.url, where.params, function (data) {
							if (data && (data.requestId === undefined || data.requestId >= ctx.requestId)) {
								options.parseResponse(ctx, data)
								ctx.cache[needle] = data
							}
						});
					},

					selectCurrent: function (withoutFocus) {
						if (this.selected >= 0 && this.selected < this.variants.length)
							this.target.value = this.variants[this.selected]
						//this.hide();
						if (!withoutFocus)
							this.target.focus();
					},

					keyup: function (evt) {
						switch(evt.keyCode) {
						case 9: // tab
							this.hide()
							return false
						break;
						case 27: // esc
							this.hide()
							return false
						break;
						case 40: // down
							var i = this.selected + 1
							if (i >= this.variants.length)
								i = this.variants.length - 1
							options.selectItem(this, i)
							this.selectCurrent()
							this.show()
							return false
						break;
						case 38: // up
							var i = this.selected - 1
							if (i < -1)
								i = -1
							options.selectItem(this, i)
							this.selectCurrent()
							this.show()
							return false
						break;
						case 13: // enter
							this.selectCurrent()
							this.cancelEvent(evt)
							return false
						break;
						}
						this.refresh()
						return true
					}
				}

				api.init(e);

				$(e).focusin(function() {
					api.acquire()
				}).focusout(function() {
					api.selectCurrent(true)
					api.hide()
					$.ajp.autocomplete.last = api
				}).keyup(function(evt) {
					api.keyup(evt)
				}).attr('autocomplete', 'off')
			})
		}
	})
})(jQuery);
